package service

import (
	"context"
	"encoding/json"
	"fmt"
	baseStore "gitee.com/zaiqiang231/go-base-app/base_app/store"
	sessionHandler "gitee.com/zaiqiang231/go-base-session/app/http/handler"
	"gitee.com/zaiqiang231/zcamp-service-camp/app/model"
	"gitee.com/zaiqiang231/zcamp-service-camp/app/model/req"
	remoteRpc "gitee.com/zaiqiang231/zcamp-service-camp/app/rpc/remote"
	"gitee.com/zaiqiang231/zcamp-service-camp/app/store/cache"
	"gitee.com/zaiqiang231/zcamp-service-camp/app/store/repo"
	userModel "gitee.com/zaiqiang231/zcamp-service-user/app/model"
	"gitee.com/zaiqiang231/zcamp-service-user/app/rpc/proto"
	userProto "gitee.com/zaiqiang231/zcamp-service-user/app/rpc/proto"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"strconv"
	"time"
)

type ServicePost struct{}

var ServicePostInstance = new(ServicePost)

func (service *ServicePost) MyCampingData() (data model.MyCampingData, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		data.CampingCount = 3
		data.CityCount = 2
		data.PunchCount = 1
		data.LastCampingEndTime = time.Now().Unix()
	})
	return
}

func (service *ServicePost) PostCreate(request req.PostCreateReq) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		err = db.Transaction(func(tx *gorm.DB) error {
			txErr := tx.Create(&request.PostInfo).Error
			if txErr != nil {
				return txErr
			}
			return nil
		})
	})
	return
}

func (service *ServicePost) PostUpdateForMap(info map[string]interface{}) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		err = db.Transaction(func(tx *gorm.DB) error {
			var old model.PostInfo
			var txErr error
			if errors.Is(tx.Where("post_id = ?", info["post_id"]).First(&old).Error, gorm.ErrRecordNotFound) {
				txErr = errors.New("原记录不存在")
			}
			if txErr != nil {
				return txErr
			}

			delete(info, "updated_at")
			txErr = tx.Model(&old).Omit("id", "post_id", "created_at", "deleted_at").Updates(info).Error
			if txErr != nil {
				return txErr
			}

			return nil
		})

	})
	return err
}

func (service *ServicePost) DeletePost(post_id string, create_uid string) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		var tmp model.PostInfo
		err = db.Model(&model.PostInfo{}).Where("post_id = ?", post_id).Where("uid = ?", create_uid).First(&tmp).Delete(&tmp).Error
	})
	return
}

func (service *ServicePost) DeletePostList(info req.IdsStringReq) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		var tmpList []model.PostInfo
		err = db.Model(&model.PostInfo{}).Where("post_id in ?", info.Ids).Find(&tmpList).Delete(&tmpList).Error
	})
	return
}

func (service *ServicePost) PostOperate(uid int64, request req.OperatePostReq) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {

		var userAdmin userModel.UserAdmin
		isPostAdmin := false
		tmperr := db.
			Where("uid = ?", uid).
			First(&userAdmin).Error
		if tmperr == nil {
			isPostAdmin = userAdmin.PostAdmin == 1
		}

		if isPostAdmin {
			var tmp model.PostInfo
			err = db.Model(&model.PostInfo{}).Where("post_id = ?", request.PostId).
				Find(&tmp).Delete(&tmp).Error
		}

	})
	return
}

func (service *ServicePost) GetPostList(info req.PostListReq) (list interface{}, total int64, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		limit := info.PageSize
		offset := info.PageSize * (info.Page - 1)
		var tempDb = db
		if info.ID != 0 {
			tempDb = tempDb.Where("post_id = ?", info.PostID)
		}
		if info.Title != "" {
			tempDb = tempDb.Where("camp_name LIKE ?", "%"+info.Title+"%")
		}
		if info.Status != 0 {
			tempDb = tempDb.Where("status = ?", info.Status)
		}
		var tmpList []model.PostInfo
		err = tempDb.Model(&model.PostInfo{}).Count(&total).Error
		if err != nil {
			return
		}
		err = tempDb.Model(&model.PostInfo{}).Offset(offset).Limit(limit).Find(&tmpList).Error
		if err != nil {
			return
		}
		list = tmpList
	})
	return
}

func (service *ServicePost) GetPostDetail(info req.PostDetailReq) (data model.PostInfo, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		var tempDb = db
		tempDb = tempDb.Where("post_id = ?", info.PostId).Where("uid = ?", info.CreateUid)
		err = tempDb.Model(&model.PostInfo{}).First(&data).Error
		if err != nil {
			return
		}
	})
	return
}

func (service *ServicePost) GetFollowList(ctx *gin.Context, info req.CursorInfo) (list []model.PostingDetail, cursor string, hasMore bool, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		atom := sessionHandler.GetAtom(ctx)
		userResponse, temperr := remoteRpc.GetUserRpc().UserRpcServiceClient.GetUserFollowList(ctx, &userProto.UserFollowListRequest{
			Uid: atom.Uid,
		})
		if temperr != nil {
			err = temperr
			return
		}

		var followUids []int64
		uidMap := make(map[int64]proto.UserInfoResponse)
		for _, response := range userResponse.List {
			followUids = append(followUids, response.Uid)
			uidMap[response.Uid] = *response
		}

		limit := info.PageSize + 1
		var tempDb = db

		if info.Cursor != "" {
			tempDb = tempDb.Where("id < ?", info.Cursor)
		}

		var tmpList []model.PostInfo
		err = tempDb.Model(&model.PostInfo{}).Where("uid in (?)", followUids).Order("id desc").Limit(limit).Find(&tmpList).Error
		if err != nil {
			return
		}

		length := len(tmpList)
		hasMore = length == limit
		if hasMore {
			tmpList = tmpList[:length-1]
		}
		if len(tmpList) > 0 {
			cursor = strconv.Itoa(int(tmpList[len(tmpList)-1].ID))
		}

		for _, postInfo := range tmpList {
			temp := model.PostingDetail{}
			temp.Post = model.PostMoreInfo{
				PostInfo: postInfo,
			}
			temp.User = model.PostUser{
				Uid:       uidMap[postInfo.Uid].Uid,
				NickName:  uidMap[postInfo.Uid].NickName,
				HeaderImg: uidMap[postInfo.Uid].HeaderImg,
			}
			list = append(list, temp)
		}

	})
	return
}

func (service *ServicePost) GetRecommendList(ctx context.Context, info req.RecommendListReq) (list []model.PostingDetail, cursor string, hasMore bool, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		limit := info.PageSize + 1
		var tempDb = db

		if info.Cursor != "" {
			tempDb = tempDb.Where("id < ?", info.Cursor)
		}

		var tmpList []model.PostInfo
		err = tempDb.Model(&model.PostInfo{}).Order("id desc").Limit(limit).Find(&tmpList).Error
		if err != nil {
			return
		}

		uids := make([]int64, 0)
		uidMap := make(map[int64]proto.UserInfoResponse)
		for _, postInfo := range tmpList {
			uidMap[postInfo.Uid] = proto.UserInfoResponse{}
		}
		for k, _ := range uidMap {
			uids = append(uids, k)
		}

		userResponse, temperr := remoteRpc.GetUserRpc().UserRpcServiceClient.GetUserInfoList(ctx, &userProto.UserInfoListRequest{
			Uids: uids,
		})
		if temperr != nil {
			err = temperr
			return
		}
		for _, userInfo := range userResponse.List {
			uidMap[userInfo.Uid] = *userInfo
		}

		length := len(tmpList)
		hasMore = length == limit
		if hasMore {
			tmpList = tmpList[:length-1]
		}
		if len(tmpList) > 0 {
			cursor = strconv.Itoa(int(tmpList[len(tmpList)-1].ID))
		}

		for _, postInfo := range tmpList {
			temp := model.PostingDetail{}
			temp.Post = model.PostMoreInfo{
				PostInfo: postInfo,
			}
			temp.User = model.PostUser{
				Uid:       uidMap[postInfo.Uid].Uid,
				NickName:  uidMap[postInfo.Uid].NickName,
				HeaderImg: uidMap[postInfo.Uid].HeaderImg,
			}
			list = append(list, temp)
		}

	})
	return
}

func (service *ServicePost) NearByList(ctx context.Context, info req.NearByListReq) (list []model.PostingDetailWithDistance, cursor string, hasMore bool, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		limit := info.PageSize + 1
		var tempDb = db

		if info.Cursor != "" {
			tempDb = tempDb.Where("id < ?", info.Cursor)
		}

		if info.Longitude != "" && info.Latitude != "" {
			selectSql := fmt.Sprintf("*, CAST((6371000 * acos(cos(radians(%v))* cos(radians(latitude))* cos(radians(longitude) - radians(%v))+ sin(radians(%v))* sin(radians(latitude)))) AS UNSIGNED) AS distance", info.Latitude, info.Longitude, info.Latitude)
			tempDb = tempDb.Select(selectSql)
		} else {
			selectSql := "*, 0 AS distance"
			tempDb = tempDb.Select(selectSql)
		}

		tempDb = tempDb.Order("distance ASC, id desc").Having("distance >= ?", 0)

		var tmpList []model.PostInfoWithDistance
		err = tempDb.Model(&model.PostInfoWithDistance{}).Limit(limit).Find(&tmpList).Error
		if err != nil {
			return
		}

		uids := make([]int64, 0)
		uidMap := make(map[int64]proto.UserInfoResponse)
		for _, postInfo := range tmpList {
			uidMap[postInfo.Uid] = proto.UserInfoResponse{}
		}
		for k, _ := range uidMap {
			uids = append(uids, k)
		}

		userResponse, temperr := remoteRpc.GetUserRpc().UserRpcServiceClient.GetUserInfoList(ctx, &userProto.UserInfoListRequest{
			Uids: uids,
		})
		if temperr != nil {
			err = temperr
			return
		}
		for _, userInfo := range userResponse.List {
			uidMap[userInfo.Uid] = *userInfo
		}

		length := len(tmpList)
		hasMore = length == limit
		if hasMore {
			tmpList = tmpList[:length-1]
		}
		if len(tmpList) > 0 {
			cursor = strconv.Itoa(int(tmpList[len(tmpList)-1].ID))
		}

		for _, postInfo := range tmpList {
			temp := model.PostingDetailWithDistance{}
			temp.Post = postInfo
			temp.Distance = postInfo.Distance
			temp.User = model.PostUser{
				Uid:       uidMap[postInfo.Uid].Uid,
				NickName:  uidMap[postInfo.Uid].NickName,
				HeaderImg: uidMap[postInfo.Uid].HeaderImg,
			}
			list = append(list, temp)
		}

	})
	return
}

func (service *ServicePost) PunchList(ctx context.Context, info req.PunchListReq) (list []model.PostingDetail, cursor string, hasMore bool, count int64, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		limit := info.PageSize + 1
		var tempDb = db

		if info.Cursor != "" {
			tempDb = tempDb.Where("id < ?", info.Cursor)
		}

		if info.CampId > 0 {
			tempDb = tempDb.Where("camp_id = ?", info.CampId)
		}

		err = tempDb.Model(&model.PostInfo{}).Count(&count).Error
		if err != nil {
			return
		}
		var tmpList []model.PostInfo
		err = tempDb.Model(&model.PostInfo{}).Order("id desc").Limit(limit).Find(&tmpList).Error
		if err != nil {
			return
		}

		uids := make([]int64, 0)
		uidMap := make(map[int64]proto.UserInfoResponse)
		for _, postInfo := range tmpList {
			uidMap[postInfo.Uid] = proto.UserInfoResponse{}
		}
		for k, _ := range uidMap {
			uids = append(uids, k)
		}

		userResponse, temperr := remoteRpc.GetUserRpc().UserRpcServiceClient.GetUserInfoList(ctx, &userProto.UserInfoListRequest{
			Uids: uids,
		})
		if temperr != nil {
			err = temperr
			return
		}
		for _, userInfo := range userResponse.List {
			uidMap[userInfo.Uid] = *userInfo
		}

		length := len(tmpList)
		hasMore = length == limit
		if hasMore {
			tmpList = tmpList[:length-1]
		}
		if len(tmpList) > 0 {
			cursor = strconv.Itoa(int(tmpList[len(tmpList)-1].ID))
		}

		for _, postInfo := range tmpList {
			temp := model.PostingDetail{}
			temp.Post = model.PostMoreInfo{
				PostInfo: postInfo,
			}
			temp.User = model.PostUser{
				Uid:       uidMap[postInfo.Uid].Uid,
				NickName:  uidMap[postInfo.Uid].NickName,
				HeaderImg: uidMap[postInfo.Uid].HeaderImg,
			}
			list = append(list, temp)
		}

	})
	return
}

func (service *ServicePost) PostDetailList(ctx context.Context, pids []int64) (list []model.PostingDetail, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		var tempDb = db
		var tmpList []model.PostInfo
		err = tempDb.Model(&model.PostInfo{}).Where("post_id in (?)", pids).Find(&tmpList).Error
		if err != nil {
			return
		}

		uids := make([]int64, 0)
		uidMap := make(map[int64]proto.UserInfoResponse)
		for _, postInfo := range tmpList {
			uidMap[postInfo.Uid] = proto.UserInfoResponse{}
		}
		for k, _ := range uidMap {
			uids = append(uids, k)
		}

		userResponse, temperr := remoteRpc.GetUserRpc().UserRpcServiceClient.GetUserInfoList(ctx, &userProto.UserInfoListRequest{
			Uids: uids,
		})
		if temperr != nil {
			err = temperr
			return
		}
		for _, userInfo := range userResponse.List {
			uidMap[userInfo.Uid] = *userInfo
		}

		for _, postInfo := range tmpList {
			temp := model.PostingDetail{}
			temp.Post = model.PostMoreInfo{
				PostInfo: postInfo,
			}
			temp.User = model.PostUser{
				Uid:       uidMap[postInfo.Uid].Uid,
				NickName:  uidMap[postInfo.Uid].NickName,
				HeaderImg: uidMap[postInfo.Uid].HeaderImg,
			}
			list = append(list, temp)
		}

	})
	return
}

func (service *ServicePost) SubjectCreate(request req.SubjectCreateReq) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		err = db.Transaction(func(tx *gorm.DB) error {
			txErr := tx.Create(&request.Subject).Error
			if txErr != nil {
				return txErr
			}
			return nil
		})
	})
	return
}

func (service *ServicePost) SubjectUpdateForMap(info map[string]interface{}) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		err = db.Transaction(func(tx *gorm.DB) error {
			var old model.Subject
			var txErr error
			if errors.Is(tx.Where("id = ?", info["id"]).First(&old).Error, gorm.ErrRecordNotFound) {
				txErr = errors.New("原记录不存在")
			}
			if txErr != nil {
				return txErr
			}

			delete(info, "updated_at")
			txErr = tx.Model(&old).Omit("id", "created_at", "deleted_at").Updates(info).Error
			if txErr != nil {
				return txErr
			}

			return nil
		})

	})
	return err
}

func (service *ServicePost) DeleteSubjectList(info req.IdsReq) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		var tmpList []model.Subject
		err = db.Model(&model.Subject{}).Where("id in ?", info.Ids).Find(&tmpList).Delete(&tmpList).Error
	})
	return
}

func (service *ServicePost) GetSubjectList(info req.SubjectListReq) (list interface{}, total int64, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		limit := info.PageSize
		offset := info.PageSize * (info.Page - 1)
		var tempDb = db
		if info.ID != 0 {
			tempDb = tempDb.Where("id = ?", info.ID)
		}
		if info.Name != "" {
			tempDb = tempDb.Where("camp_name LIKE ?", "%"+info.Name+"%")
		}
		if info.Status != 0 {
			tempDb = tempDb.Where("status = ?", info.Status)
		}
		var tmpList []model.Subject
		err = tempDb.Model(&model.Subject{}).Count(&total).Error
		if err != nil {
			return
		}
		err = tempDb.Model(&model.Subject{}).Offset(offset).Limit(limit).Find(&tmpList).Error
		if err != nil {
			return
		}
		list = tmpList
	})
	return
}

func (service *ServicePost) SubjectList() (list interface{}, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		var tmpList []model.Subject
		err = db.Model(&model.Subject{}).Find(&tmpList).Order("weight").Error
		if err != nil {
			return
		}
		list = tmpList
	})
	return
}

func (service *ServicePost) SubjectPostCreate(request model.SubjectPost) (err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		err = db.Transaction(func(tx *gorm.DB) error {
			txErr := tx.Create(&request).Error
			if txErr != nil {
				return txErr
			}
			return nil
		})
	})
	return
}

func (service *ServicePost) SubjectContentList(ctx context.Context, request req.SubjectContentListReq) (list interface{}, hasMore bool, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		limit := request.PageSize
		offset := request.PageSize * (request.Page - 1)
		var tmpList []model.SubjectPost
		err = db.Model(&model.SubjectPost{}).Where("subject_id = ?", request.SubjectID).Offset(offset).Limit(limit).Find(&tmpList).Order("weight").Error
		if err != nil {
			return
		}
		hasMore = len(tmpList) == limit
		var pids []int64

		for _, post := range tmpList {
			pids = append(pids, post.PostID)
		}

		postDetails, tmperr := ServicePostInstance.PostDetailList(ctx, pids)
		if tmperr != nil {
			err = tmperr
			return
		}
		list = postDetails
	})
	return
}

func (service *ServicePost) PostRecord(ctx *gin.Context, request req.PostRecordReq) (err error) {
	atom := sessionHandler.GetAtom(ctx)
	postID, err := strconv.ParseInt(request.PostId, 10, 64)
	if err != nil {
		return
	}
	postUID, err := strconv.ParseInt(request.CreateUid, 10, 64)
	if err != nil {
		return
	}
	if postUID != atom.Uid {
		err = cache.GetPostCache().AddPostDetailView(ctx, postID, atom.Uid, atom.Smid)
	}
	return
}

func (service *ServicePost) PostLikeOrNot(ctx *gin.Context, request req.PostLikeOrNotReq, like bool) (row model.PostLike, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		atom := sessionHandler.GetAtom(ctx)
		if like {
			_, tmperr := service.GetPostDetail(req.PostDetailReq{
				PostId:    request.PostId,
				CreateUid: request.CreateUid,
			})
			if tmperr != nil {
				err = tmperr
				return
			}
			err = db.Model(model.PostLike{}).
				Where("post_id = ?", request.PostId).
				Where("like_uid = ?", atom.Uid).
				Assign(model.PostLike{
					PostID:    request.PostId,
					CreateUID: request.CreateUid,
					LikeUID:   strconv.FormatInt(atom.Uid, 10),
				}).
				FirstOrCreate(&row).
				Error
		} else {
			err = db.Model(model.PostLike{}).
				Where("post_id = ?", request.PostId).
				Where("like_uid = ?", atom.Uid).
				Unscoped().
				Delete(&row).
				Error
		}

	})
	return
}

func (service *ServicePost) GetViewCountByPostIDs(ctx context.Context, postIDs []uint64) (map[string]int, error) {
	res := make(map[string]int)
	statisticsByPostIDs, err := repo.GetPostRepository().FindStatisticsByPostIDs(postIDs)
	if err != nil {
		return res, fmt.Errorf("FindStatisticsByPostIDs err: %w", err)
	}
	for _, item := range statisticsByPostIDs {
		postIDStr := strconv.FormatInt(item.PostID, 10)
		res[postIDStr] = item.ViewCount
	}

	// 今日帖子浏览数
	todayStr := time.Now().Format("20060102")
	todayViewMap, err := cache.GetPostCache().GetPostDetailViewByPostIDs(ctx, todayStr, postIDs)
	if err != nil {
		return res, fmt.Errorf("GetPostDetailViewByPostIDs err: %w", err)
	}
	for postID, count := range todayViewMap {
		if count > 0 {
			res[postID] += count
		}
	}
	return res, nil
}

func (service *ServicePost) FlushYesterdayView(ctx context.Context) error {
	yesterday := time.Now() //.AddDate(0, 0, -1)
	dateStr := yesterday.Format("20060102")
	if err := service.flushViewByDate(ctx, dateStr); err != nil {
		return err
	}
	return nil
}

func (service *ServicePost) flushViewByDate(ctx context.Context, date string) error {
	viewMap, err := cache.GetPostCache().GetPostDetailViewMap(ctx, date)
	if err != nil {
		return err
	}

	for key, count := range viewMap {
		if count <= 0 {
			continue
		}
		postID, errtmp := strconv.ParseUint(key, 10, 64)
		if errtmp != nil {
			continue
		}

		errtmp = repo.GetPostRepository().AddStatisticsView(postID, count)
		if errtmp != nil {
			continue
		}
	}

	// 删除缓存
	err = cache.GetPostCache().DelPostDetailViewMap(ctx, date)
	if err != nil {
		return err
	}

	return nil
}

func (service *ServicePost) TopicSearchList(ctx context.Context, request req.TopicSearchReq) (list []model.TopicInfo, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		var tempDb = db.Model(model.Topic{})
		if request.Keyword != "" {
			tempDb = tempDb.Where("name LIKE ?", "%"+request.Keyword+"%")
		}
		var tmpList []model.Topic
		err = tempDb.Find(&tmpList).Order("weight").Error
		if err != nil {
			return
		}

		for _, topic := range tmpList {
			imageInfo := model.TopicDetailImage{}
			_ = json.Unmarshal([]byte(topic.ImageJSON), &imageInfo)
			list = append(list, model.TopicInfo{
				Name:  topic.Name,
				Image: imageInfo,
			})
		}
	})
	return
}

func (service *ServicePost) TopicDetail(ctx context.Context, request req.TopicDetailReq) (data model.TopicInfo, err error) {
	baseStore.GetDataStore().Execute(func(db *gorm.DB) {
		var topic model.Topic
		err = db.Model(model.Topic{}).Where("name = ?", request.Name).First(&topic).Order("weight").Error
		if err != nil {
			return
		}

		imageInfo := model.TopicDetailImage{}
		_ = json.Unmarshal([]byte(topic.ImageJSON), &imageInfo)
		data = model.TopicInfo{
			Name:  topic.Name,
			Image: imageInfo,
		}
	})
	return
}
